using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;

namespace Pinwheel.LSerialize
{
    public unsafe partial struct LStreamWriter : IDisposable
    {
        public static LStreamWriter* Default;
        static ILFormatter<ValueHeader> ValFormatter;
        static ILFormatter<ReferenceHeader> RefFormatter;
        
        private LStreamBuffer* buffer;
        public LStreamBuffer* Buffer => buffer == default ? LStreamBuffer.Create() : buffer;

        // private ILStreamBufferWriter writer;

        static LStreamWriter()
        {
            Default = Native.Current.AllocAndClear<LStreamWriter>();
            Default->buffer = LStreamBuffer.Create();
            ValFormatter = LFormatterProvider.GetFormatter<ValueHeader>();
            RefFormatter = LFormatterProvider.GetFormatter<ReferenceHeader>();
        }
        
        public LStreamWriter(int size = LStreamBuffer.DefaultSize)
        {
            buffer = LStreamBuffer.Create(size);
        }
        
        public void Dispose()
        {
            buffer->Dispose();
            Native.Current.Free(buffer);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Seek(int offset, SeekOrigin loc) => Buffer->Seek(offset, loc);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] GetBuffer() => Buffer->GetBuffer();
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int GetBuffer(byte* buffer) => Buffer->GetBuffer(buffer);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ILFormatter<T> GetFormatter<T>() => LFormatterProvider.GetFormatter<T>();
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ILFormatter GetFormatter(Type type) => LFormatterProvider.GetFormatter(type);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteByte(byte value)
        {
            return Buffer->PushByte(value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteUInt16(UInt16 value)
        {
            // int size = 0;
            // size += WriteByte((byte) (value & 0xff));
            // size += WriteByte((byte) ((value >> 8) & 0xff));
            int size = sizeof(UInt16);
            var ptr = &value;
            Buffer->PushBytes((byte*)ptr, size);
            return size;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteUInt32(UInt32 value)
        {
            // int size = 0;
            // size += WriteByte((byte) (value & 0xff));
            // size += WriteByte((byte) ((value >> 8) & 0xff));
            // size += WriteByte((byte) ((value >> 16) & 0xff));
            // size += WriteByte((byte) ((value >> 24) & 0xff));
            int size = sizeof(UInt32);
            var ptr = &value;
            Buffer->PushBytes((byte*)ptr, size);
            return size;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteUInt64(UInt64 value)
        {
            // int size = 0;
            // size += WriteByte((byte) (value & 0xff));
            // size += WriteByte((byte) ((value >> 8) & 0xff));
            // size += WriteByte((byte) ((value >> 16) & 0xff));
            // size += WriteByte((byte) ((value >> 24) & 0xff));
            // size += WriteByte((byte) ((value >> 32) & 0xff));
            // size += WriteByte((byte) ((value >> 40) & 0xff));
            // size += WriteByte((byte) ((value >> 48) & 0xff));
            // size += WriteByte((byte) ((value >> 56) & 0xff));
            // return size;
            int size = sizeof(UInt64);
            var ptr = &value;
            Buffer->PushBytes((byte*)ptr, size);
            return size;
        }
        
        // [MethodImpl(MethodImplOptions.AggressiveInlining)]
        // public int WriteUInt128(UInt128 value)
        // {
        //     int size = 0;
        //     size += WriteByte((byte) (value & 0xff));
        //     size += WriteByte((byte) ((value >> 8) & 0xff));
        //     size += WriteByte((byte) ((value >> 16) & 0xff));
        //     size += WriteByte((byte) ((value >> 24) & 0xff));
        //     size += WriteByte((byte) ((value >> 32) & 0xff));
        //     size += WriteByte((byte) ((value >> 40) & 0xff));
        //     size += WriteByte((byte) ((value >> 48) & 0xff));
        //     size += WriteByte((byte) ((value >> 56) & 0xff));
        //     size += WriteByte((byte) ((value >> 64) & 0xff));
        //     size += WriteByte((byte) ((value >> 72) & 0xff));
        //     size += WriteByte((byte) ((value >> 80) & 0xff));
        //     size += WriteByte((byte) ((value >> 88) & 0xff));
        //     size += WriteByte((byte) ((value >> 96) & 0xff));
        //     size += WriteByte((byte) ((value >> 104) & 0xff));
        //     size += WriteByte((byte) ((value >> 112) & 0xff));
        //     size += WriteByte((byte) ((value >> 120) & 0xff));
        //     return size;
        // }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteUnmanagedValue<T>(ref T value) where T : unmanaged
        {
            fixed (void* ptr = &value)
            {
                return WriteUnmanagedValue((byte*)ptr, sizeof(T));
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteUnmanagedValue(byte* ptr, int length) => Buffer->PushBytes(ptr, length);


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteUnmanagedArray<T>(ref T[] value) where T : unmanaged
        {
            var size = value.Length * sizeof(T);
            Buffer->EnsureSize(size + sizeof(uint));
            void* ptr = Unsafe.AsPointer(ref value);
            WriteUInt32((uint)value.Length);
            WriteUnmanagedValue((byte*)ptr, size);
            return size;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int UnsafeWriteUnmanagedArray<T>(ref T[] value)
        {
            var size = value.Length * Unsafe.SizeOf<T>();
            Buffer->EnsureSize(size + sizeof(uint));
            void* ptr = Unsafe.AsPointer(ref value);
            WriteUInt32((uint)value.Length);
            WriteUnmanagedValue((byte*)ptr, size);
            return size;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteBytes(byte[] value)
        {
            fixed (byte* ptr = &value[0])
            {
                return WriteBytes(ptr, value.Length);
            }
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteBytes(byte* ptr, int length)
        {
            int size = sizeof(uint);
            Buffer->EnsureSize(size);
            WriteUInt32((uint)length);
            return Buffer->PushBytes(ptr, length) + size;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int WriteString(string value)
        {
            return WriteBytes(Encoding.UTF8.GetBytes(value));
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Clear() => Buffer->Clear();
        
    }
}